/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.classes;

import org.griphyn.vdl.classes.*;
import java.util.*;
import java.io.IOException;
import java.io.Writer;
import java.io.Serializable;

/**
 * This class implements the list argument type used for parameters passed
 * to transformations from derivations. 
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 *
 * @see Value
 * @see Scalar
 */
public class List extends Value implements Cloneable, Serializable
{
  /**
   * A list is just an ordered bunch of {@link Scalar}.
   */
  private ArrayList m_scalarList;

  /**
   * Creates and returns a copy of this object.
   * @return a new instance.
   */
  public Object clone()
  {
    List result = new List();
    for ( int index=0; index < this.m_scalarList.size(); ++index ) {
      result.addScalar( (Scalar) this.getScalar(index).clone() );
    }
    return result;
  }

  /**
   * Default ctor.
   */
  public List()
  {
    super();
    this.m_scalarList = new ArrayList();
  }

  /**
   * Convenience ctor: Initializes the list, and stores the given 
   * {@link Scalar} as first child into the list. 
   *
   * @param firstChild is the first element in the list
   */
  public List( Scalar firstChild )
  {
    super();
    this.m_scalarList = new ArrayList();
    this.m_scalarList.add(firstChild);
  }

  /**
   * Accessor: Obtains the value type of this class. By using the abstract
   * method in the parent class, <code>List</code> objects can be
   * distinguished from <code>Scalar</code> objects without using the
   * <code>instanceof</code> operator.
   *
   * @return the fixed value of being a scalar. 
   * @see Value#LIST 
   */
  public int getContainerType()
  { 
    return Value.LIST; 
  }

  /**
   * This method determines which container is being used in the abstract 
   * base class in order to kludgy statements when printing debug info.
   *
   * @return the symblic identifier for the type of the Value.
   */
  public String getSymbolicType()
  {
    // always
    return new String("List");
  }
  
  /**
   * Accessor: Appends as <code>Scalar</code> value to the list.
   *
   * @param vScalar is the <code>Scalar</code> to append to the list.
   * @throws IndexOutOfBoundsException if the value cannot be added.
   * @see Scalar
   */
  public void addScalar( Scalar vScalar )
    throws IndexOutOfBoundsException
  {
    this.m_scalarList.add(vScalar); 
  }
  
  /**
   * Accessor: Insert a <code>Scalar</code> at a specific position.
   *
   * @param index is the position to insert the item into
   * @param vScalar is the <code>Scalar</code> to append to the list.
   * @throws IndexOutOfBoundsException if the value cannot be added.
   * @see Scalar
   */
  public void addScalar( int index, Scalar vScalar )
    throws IndexOutOfBoundsException
  {
    this.m_scalarList.add(index, vScalar); 
  }

  /**
   * Accessor: constructs the iterator for the <code>List</code> items.
   * 
   * @return an enumeration to walk the list with.
   * @deprecated Use the new Collection based interfaces
   */
  public Enumeration enumerateScalar()
  {
    return Collections.enumeration(this.m_scalarList); 
  }

  /**
   * Determines all LFN instances of a given scalar that match the
   * specified linkage. This is a higher-level method employing the
   * given API.
   *
   * @param linkage is the linkage to check for, -1 for all filenames.
   * @return a set of logical filename instances that match the linkage
   * and were part of the scalar. The result may be an empty set, if no
   * such result were to be found.
   *
   * @see Scalar#getAllLFN( int )
   * @see LFN 
   */
  public java.util.List getAllLFN( int linkage )
  {
    java.util.List result = new ArrayList();

    for ( Iterator i=this.iterateScalar(); i.hasNext(); )
      result.addAll( ((Scalar) i.next()).getAllLFN(linkage) );
    
    return result;
  }

  /**
   * Determines all LFN instances of a given scalar that match the
   * specified linkage. This is a higher-level method employing the
   * given API. Note that also linkage of NONE will not be found in
   * wildcard search mode.
   *
   * @param linkage is the linkage to check for, -1 for all filenames.
   * @return a set of all logical filenames that match the linkage and
   * were part of the scalar. The result may be an empty set, if no such
   * result were to be found. For a linkage of -1, complete LFNs will be
   * returned, for any other linkage, just the filename will be
   * returned.
   *
   * @see Scalar#getLFNList( int ) 
   * @see Derivation#getLFNList( int ) 
   * @see LFN 
   */
  public java.util.List getLFNList( int linkage )
  {
    java.util.List result = new ArrayList();

    for ( Iterator i=this.iterateScalar(); i.hasNext(); )
      result.addAll( ((Scalar) i.next()).getLFNList(linkage) );
    
    return result;
  }

  /**
   * Determines if the list contains an LFN of the specified linkage.
   * The logic uses short-circuit evaluation, thus finding things is 
   * faster than not finding things. Searching a list is a potentially
   * expensive method.
   *
   * @param filename is the name of the LFN
   * @param linkage is the linkage to check for, -1 for any linkage type.
   * @return true if the LFN is contained in the scalar, false otherwise.
   *
   * @see org.griphyn.vdl.classes.LFN 
   * @see Scalar#containsLFN( String, int )
   */
  public boolean containsLFN( String filename, int linkage ) 
  {
    for ( Iterator i=this.iterateScalar(); i.hasNext(); ) 
      if ( ((Scalar) i.next()).containsLFN( filename, linkage ) ) 
	return true;

    // not found
    return false;
  }  
  
  /**
   * Accessor: Obtains the value of a specific item in the list.
   *
   * @param index is the position of which to obtain the value of.
   * @return The {@link Scalar} at the specified position.
   * @throws IndexOutOfBoundsException if the index points to an element
   *         that is beyond the list boundaries.
   */
  public Scalar getScalar(int index)
    throws IndexOutOfBoundsException
  {
    //-- check bound for index
    if ((index < 0) || (index >= this.m_scalarList.size())) 
      throw new IndexOutOfBoundsException();

    return (Scalar) this.m_scalarList.get(index);
  }

  /**
   * Accessor: Gets an array of all <code>Scalar</code>s in the list.
   *
   * @return an array of <code>Scalar</code>s.
   * @see Scalar
   * @deprecated Use the new Collection based interfaces
   */
  public Scalar[] getScalar()
  {
    int size = this.m_scalarList.size();
    Scalar[] mScalar = new Scalar[size];
    System.arraycopy( this.m_scalarList.toArray( new Scalar[0] ), 0,
		      mScalar, 0, size );
    return mScalar;
  }

  /**
   * Accessor: Obtains the element count of the internal list
   *
   * @return number of elements in the internal list
   */
  public int getScalarCount()
  { return this.m_scalarList.size(); }

  /**
   * Accessor: Gets an array of all <code>Scalar</code>s in the list.
   * This list is read-only. 
   *
   * @return an array of <code>Scalar</code>s.
   * @see Scalar
   */
  public java.util.List getScalarList()
  {
    return Collections.unmodifiableList(this.m_scalarList);
  }

  /**
   * Accessor: constructs the iterator for the <code>List</code> items.
   * 
   * @return an enumeration to walk the list with.
   */
  public Iterator iterateScalar()
  {
    return this.m_scalarList.iterator();
  }

  /**
   * Accessor: constructs the iterator for the <code>List</code> items.
   * 
   * @return an enumeration to walk the list with.
   */
  public ListIterator listIterateScalar()
  {
    return this.m_scalarList.listIterator();
  }

  /**
   * Accessor: constructs the iterator for the <code>List</code> items.
   * @param start is the starting position for the sub-iteration.
   * @return an enumeration to walk the list with.
   */
  public ListIterator listIterateScalar( int start )
  {
    return this.m_scalarList.listIterator(start);
  }


  /**
   * Accessor: Removes all elements in the <code>List</code>.
   */
  public void removeAllScalar()
  {
    this.m_scalarList.clear(); 
  }

  /**
   * Accessor: Removes a single element from the <code>List</code>. Each
   * component in this vector with an index greater or equal to the
   * specified index is shifted downward to have an index one smaller
   * than the value it had previously. The size of this vector is
   * decreased by 1.
   *
   * @param index is the position at which an element is to be removed.
   * @return the {@link Scalar} that was removed.
   * @throws ArrayIndexOutOfBoundsException if the index was invalid.
   */
  public Scalar removeScalar( int index )
  {
    return (Scalar) this.m_scalarList.remove(index);
  }

  /**
   * Accessor: Overwrite an element at a given position.
   * 
   * @param index is the position to use. It must be within the list.
   * @param vScalar is the new value to replace the element with.
   * @throws IndexOutOfBoundsException if the position is outside the list.
   */
  public void setScalar(int index, Scalar vScalar)
    throws IndexOutOfBoundsException
  {
    //-- check bounds for index
    if ((index < 0) || (index >= this.m_scalarList.size())) {
      throw new IndexOutOfBoundsException();
    }
    this.m_scalarList.set(index, vScalar);
  } //-- void setScalar(int, Scalar) 

  /**
   * Accessor: Replaces all elements with a new list of {@link Scalar}s.
   * 
   * @param scalarArray is the list to replace the original list with.
   * @deprecated Use the new Collection based interfaces
   */
  public void setScalar(Scalar[] scalarArray)
  {
    //-- copy array
    this.m_scalarList.clear();
    this.m_scalarList.addAll( Arrays.asList(scalarArray) );
  }

  /**
   * Accessor: Replaces all elements with a new list of {@link Scalar}s.
   * 
   * @param scalars is the list to replace the original list with.
   */
  public void setScalar(Collection scalars)
  {
    this.m_scalarList.clear();
    this.m_scalarList.addAll( scalars );
  }

  /**
   * Dumps the list and all its contents into a string. The list will
   * be terminated by brackets, elements separated by komma, space. 
   * Elements itself will be dumped by recursive calls to the element
   * specific method of the same name.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @exception IOException if something fishy happens to the stream.
   */
  public void toString( Writer stream )
    throws IOException 
  {
    stream.write( "[ " );
    for ( Iterator i=this.m_scalarList.iterator(); i.hasNext(); ) { 
      ((Scalar) i.next()).toString(stream);
      if ( i.hasNext() ) stream.write( ", " );
    }
    stream.write( " ]" );
  }

  /**
   * Dump the state of the current element as XML output. This function
   * traverses all sibling classes as necessary, and converts the data
   * into pretty-printed XML output. The stream interface should be able
   * to handle large output efficiently, if you use a buffered writer.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @param indent is a <code>String</code> of spaces used for pretty
   * printing. The initial amount of spaces should be an empty string.
   * The parameter is used internally for the recursive traversal.
   * @param namespace is the XML schema namespace prefix. If neither
   * empty nor null, each element will be prefixed with this prefix,
   * and the root element will map the XML namespace. 
   * @exception IOException if something fishy happens to the stream.
   */
  public void toXML( Writer stream, String indent, String namespace )
    throws IOException
  {
    String newline = System.getProperty( "line.separator", "\r\n" );
    String tag = ( namespace != null && namespace.length() > 0 ) ?
      namespace + ":list" : "list";

    // open tag
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( '<' );
    stream.write( tag );
    stream.write( '>' );
    if ( indent != null ) stream.write( newline );

    // dump content
    String newindent = indent==null ? null : indent+"  ";
    for ( Iterator i=this.m_scalarList.iterator(); i.hasNext(); ) { 
      // FIXME: If we cast to Value, we can have lists in lists
      ((Scalar) i.next()).toXML( stream, newindent, namespace );
    }

    // close tag
    if ( indent != null && indent.length() > 0 ) stream.write( indent );
    stream.write( "</" );
    stream.write( tag );
    stream.write( '>' );
    if ( indent != null ) stream.write( newline );
  }
}

